package com.skilltrack.enrol.service.impl;


import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.skilltrack.enrol.mapper.TeachplanMapper;
import com.skilltrack.enrol.mapper.TeachplanMediaMapper;
import com.skilltrack.enrol.model.dto.SaveTeachplanDto;
import com.skilltrack.enrol.model.dto.TeachplanDto;
import com.skilltrack.enrol.model.po.Teachplan;
import com.skilltrack.enrol.model.po.TeachplanMedia;
import com.skilltrack.enrol.service.TeachplanService;
import com.skilltrackedu.exception.exception.CommonException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {

    @Autowired
    private TeachplanMapper teachplanMapper;

    @Autowired
    private TeachplanMediaMapper teachplanMediaMapper;

    /**
     * 根据课程id查询课程计划
     *
     * @param courseId 课程id
     * @return List<TeachplanDto> 课程计划
     */
    @Override
    public List<TeachplanDto> findTeachplanTree(Long courseId) {
        return teachplanMapper.selectTreeNodes(courseId);
    }

    /**
     * 新增或修改课程计划
     *
     * @param saveTeachplanDto 课程计划信息
     */
    @Transactional
    @Override
    public void saveTeachplan(SaveTeachplanDto saveTeachplanDto) {

        //通过是否有传递的id判断是修改还是新增
        Long id = saveTeachplanDto.getId();

        if (id == null) {
            //新增操作
            //把传递的dto转换为po
            Teachplan teachplan = BeanUtil.copyProperties(saveTeachplanDto, Teachplan.class);

            //设置创建时间和修改时间
            teachplan.setChangeDate(LocalDateTime.now());
            teachplan.setCreateDate(LocalDateTime.now());

            //确定排序字段,找到同级节点个数,排序字段就是个数加一,这里getTeachplanCount方法中已经+1了
            Long courseId = saveTeachplanDto.getCourseId();
            Long parentid = saveTeachplanDto.getParentid();

            //teachplan.setOrderby(getTeachplanCount(courseId, parentid));
            teachplan.setOrderby(getTeachplanMaxOrderby(courseId, parentid));

            //新增到数据库中
            int insert = teachplanMapper.insert(teachplan);

            if (insert < 1) {
                log.error("新增课程计划失败");
                throw new CommonException("新增课程计划失败");
            }
        } else {
            //修改操作
            //先查询出源数据,再将修改的数据覆盖源数据,最后更新到数据库中
            Teachplan teachplan = teachplanMapper.selectById(id);

            //将修改的数据覆盖源数据
            BeanUtil.copyProperties(saveTeachplanDto, teachplan);

            //设置修改时间
            teachplan.setChangeDate(LocalDateTime.now());

            //更新到数据库中
            int i = teachplanMapper.updateById(teachplan);
            if (i < 1) {
                log.error("修改失败");
                throw new CommonException("修改失败");
            }
        }
    }





    /**
     * 删除课程计划
     * 1、删除大章节，大章节下有小章节时不允许删除。
     * 2、删除大章节，大单节下没有小章节时可以正常删除。
     * 3、删除小章节，同时将关联的信息进行删除。
     *
     * @param teachplanId 课程计划id
     */
    @Transactional
    @Override
    public void deleteTeachplan(Long teachplanId) {

        //根据id查询课程计划是否存在
        Teachplan teachplan = teachplanMapper.selectById(teachplanId);

        //如果课程计划不存在，则抛出异常
        if (teachplan == null) {
            CommonException.cast("课程计划不存在");
        }

        //判断当前删除的课程计划是大章节还是小章节
        Integer grade = teachplan.getGrade();
        if (grade == 1) {
            //大章节

            //查询大章节下是否有小章节,如果有则不能删除,如果没有则可以删除
            int teachplanCount = getTeachplanMaxOrderby(teachplan.getCourseId(), teachplan.getId());
            if (teachplanCount == 1) {

                //该大章节下没有小章节，可以删除大章节
                int i = teachplanMapper.deleteById(teachplanId);

                if (i < 1) {
                    CommonException.cast("删除失败");
                }

            } else {
                //该大章节下有小章节，不能删除大章节
                CommonException.cast("课程计划信息还有子级信息，无法操作");
            }
        } else {
            //小章节
            //从数据库中删除课程计划以及对应的媒资信息
            int deleteTeachplan = teachplanMapper.deleteById(teachplanId);

            //如果课程计划信息删除失败，则抛出异常
            if (deleteTeachplan < 1) {
                CommonException.cast("删除失败");
            }

            //设置查询媒资信息的查询条件
            LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(TeachplanMedia::getTeachplanId, teachplanId);

            //查询当前的课程计划对应的媒资信息是否存在
            TeachplanMedia teachplanMedia = teachplanMediaMapper.selectOne(queryWrapper);

            //如果媒资信息存在，则删除媒资信息
            if (teachplanMedia != null){
                int deleteTeachplanMedia = teachplanMediaMapper.deleteById(teachplanMedia);

                //如果媒资删除失败，则抛出异常
                if (deleteTeachplanMedia < 1) {
                    CommonException.cast("删除失败");
                }
            }

            //如果媒资信息不存在,直接返回
            return;
        }
    }

    /**
     * 移动课程计划
     *
     * 上移表示将课程计划向上移动。
     * 下移表示将课程计划向下移动。
     *
     * 向上移动后和上边同级的课程计划交换位置，可以将两个课程计划的排序字段值进行交换。
     * 向下移动后和下边同级的课程计划交换位置，可以将两个课程计划的排序字段值进行交换。
     *
     * @param move       移动方向
     * @param teachplanId 课程计划id
     */
    @Transactional
    @Override
    public void move(String move, Long teachplanId) {
        /*
         * 根据课程计划ID查询课程计划信息，判断是否存在
         * 如果课程计划不存在，则抛出异常
         */
        Teachplan teachplan = teachplanMapper.selectById(teachplanId);
        if (teachplan == null) {
            CommonException.cast("课程计划不存在");
        }

        // 获取当前课程计划的排序字段orderBy的值
        Integer orderbyUp = teachplan.getOrderby();

        // 定义下(上)一个课程计划的对象
        Teachplan teachplanNext = null;

        /*
         * 判断移动方向
         * 如果是向下移动，则获取与当前课程计划同级的下一个课程计划
         * 如果是向上移动，则获取与当前课程计划同级的上一个课程计划
         */
        if ("movedown".equals(move)){
            // 向下移动
            teachplanNext = teachplanMapper.selectOne(
                new LambdaQueryWrapper<Teachplan>()
                    .eq(Teachplan::getCourseId, teachplan.getCourseId())
                    .eq(Teachplan::getParentid, teachplan.getParentid())
                    .gt(Teachplan::getOrderby, orderbyUp)// 大于当前课程计划排序字段值
                    .orderByAsc(Teachplan::getOrderby)// 升序排列
                    .last("limit 1")// 只获取一个结果
            );
        }
        if ("moveup".equals(move)){
            // 向上移动
            teachplanNext = teachplanMapper.selectOne(
                new LambdaQueryWrapper<Teachplan>()
                    .eq(Teachplan::getCourseId, teachplan.getCourseId())
                    .eq(Teachplan::getParentid, teachplan.getParentid())
                    .lt(Teachplan::getOrderby, orderbyUp)// 小于当前课程计划排序字段值
                    .orderByDesc(Teachplan::getOrderby)// 降序排列
                    .last("limit 1")// 只获取一个结果
            );
        }

        /*
         * 如果存在下(上)一个课程计划，两个课程计划的排序字段值进行交换
         * 交换完成后，更新数据库中的两个课程计划的排序字段值
         */
        if (teachplanNext != null){
            teachplan.setOrderby(teachplanNext.getOrderby());
            teachplanMapper.updateById(teachplan);
            teachplanNext.setOrderby(orderbyUp);
            teachplanMapper.updateById(teachplanNext);
        }

        // 如果不存在下一个课程计划，则直接返回
        return;
    }



    /**
     * 获取课程最大排序值
     * <p>
     * 此方法用于计算给定课程下，指定父节点的最大排序值加1
     * 主要用于在课程计划中添加新的教学计划时，确定其排序位置
     *
     * @param courseId 课程ID，用于指定查询的课程
     * @param parentId 父节点ID，用于指定查询的层级节点
     * @return 返回计算出的排序值，如果没有找到最大值，则返回1
     * <p>
     * 优点: 删除某个节点，那么该节点后面的节点的排序字段值都会-1，且新增的排序字段值不会重复
     * 确保每次查出来的orderBy值都是最后一位的(最大的那个)
     */
    private int getTeachplanMaxOrderby(long courseId, long parentId) {
        // 创建查询条件，用于获取指定课程和父节点下的最大排序值
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .eq(Teachplan::getCourseId, courseId)
                .eq(Teachplan::getParentid, parentId)
                .select(Teachplan::getOrderby)
                .orderByDesc(Teachplan::getOrderby)
                .last("LIMIT 1");

        // 执行查询并获取最大排序值
        /*Integer maxOrderby = teachplanMapper
                .selectOne(queryWrapper)
                .getOrderby();*/

        /*
        如果直接用teachplan.getOrderby() == null判断，会报错，
        因为teachplan.getOrderby()返回的是一个Integer对象，而null是一个对象，所以用teachplan.getOrderby() == null判断会报错
        */
        Teachplan teachplan = teachplanMapper.selectOne(queryWrapper);
        Integer maxOrderby = (teachplan == null) ? null : teachplan.getOrderby();
        // 如果没有找到最大值，则返回1
        return maxOrderby == null ? 1 : maxOrderby + 1;
    }

    /**
     * 获取指定条件下的教学计划节点数量
     * 该方法用于确定教学计划节点在同级节点中的排序位置
     *
     * @param courseId 课程ID，用于限定查询范围
     * @param parentId 父节点ID，用于限定查询范围
     * @return 同级节点的数量 + 1，也就是新节点的排序字段值
     * <p>
     * 缺点:如果删除某个节点，那么该节点后面的节点的排序字段值都会-1，且新增的排序字段值可能会与前一个相同
     * (1,2,3,4,5) 删掉 2 --> (1,3,4,5) 新增一个 --> (1,3,4,5,5) 由此可见删除前面的节点会导致后面新增的排序字段值会重复
     * <p>
     * 上面的getTeachplanMaxOrderby方法就能解决这个问题
     */
    private <T> int getTeachplanCount(long courseId, long parentId) {
        if (parentId == 0){
            // 如果父节点为0，则直接返回1，表示该课程下只有根节点
            return 1;
        }
        // 确定排序字段, 找到同级节点个数, 排序字段就是个数加一
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        // 设置where条件
        queryWrapper
                .eq(Teachplan::getParentid, parentId)
                .eq(Teachplan::getCourseId, courseId);

        // 查询同级节点的个数
        return teachplanMapper.selectCount(queryWrapper) + 1;
    }

}
